package com.newmainsoftech.dao.entity;

import javax.persistence.Basic;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@MappedSuperclass
public abstract class AbstractEntityIdentifierImpl implements EntityIdentifier {
	@Transient
	private Logger logger = LoggerFactory.getLogger( this.getClass());
		@Transient
		protected final Object LoggerLock = new Object();
		
		public Logger getLogger() {
			synchronized( LoggerLock) {
				return logger;
			}
		}
		protected void setLogger( final Logger logger) {
			synchronized( LoggerLock) {
				this.logger = logger;
			}
		}
	@Transient
	private static EntityIdGenerator entityIdGenerator = new EntityIdGeneratorImpl();
		public EntityIdGenerator getEntityIdGenerator() {
			return AbstractEntityIdentifierImpl.entityIdGenerator;
		}

	@Id
	@Basic( optional = false)
	private String id;
		@Override
		public String getId() {
			return id;
		}
		protected synchronized void setId( final String id) {
			this.id = id;
		}

	@Version
	@Basic( optional = false)
	private Long version;
		@Override
		public Long getVersion() {
			return version;
		}
		protected synchronized void setVersion( long version) {
			this.version = version;
		}
		
	@Override
	public boolean isPersisted() {
		return Long.class.isInstance( getVersion());
	}
	
	public AbstractEntityIdentifierImpl() {
		setId( getEntityIdGenerator().createIdStr());
	}
	
	/**
	 * Default implementation (what just uses hash values for comparison) of 
	 * <code>{@link EntityIdentifier#comp(Object)}</code> method.<br />
	 * @param obj Possibly another instance of <code>{@link EntityIdentifier}</code> type.
	 * @return 0 when 2 objects are considered as identical, negative value when obj is considered as 
	 * bigger or obj should come later, positive value when obj is considered as smaller or should come 
	 * earlier.
	 */
	@Override
	public int comp( final Object obj) {
		if ( obj == null) {
			return 1;
		}
		else {
			final int thisHashCode = this.hashCode();
			final int objHashCode = obj.hashCode();
				if ( thisHashCode == objHashCode) {
					if ( !equals( obj)) {
						throw new IllegalStateException( 
								String.format(
										"Though equals method of %1$s returned false for comaprison " 
										+ "with %2$s, %1$s and %2$s have identical hash value: %3$d",
										this.toString(), 
										obj.toString(),
										thisHashCode)
								);
					}
					return 0;
				}
			if ( thisHashCode < objHashCode) return -1;
		}
		return 1;
	}
	
	/**
	 * As default implementation, this consider of only persistence ID value being returned by 
	 * <code>{@link #getId()}</code> method.
	 * 
	 * @return hash code value.
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		
		final String id = getId();
		result = prime * result + ((id == null) ? super.hashCode() : id.hashCode());
		return result;
	}
	
	/**
	 * As default implementation, this consider of only persistence ID value being returned by 
	 * <code>{@link #getId()}</code> method as long as the object given with the <code>obj</code> 
	 * argument is an <code>{@link EntityIdentifier}</code> instance.
	 * 
	 * @param obj
	 * @return true when this instance and an object handed over via <code>obj</code> argument are 
	 * considered as identical, otherwise false.
	 */
	@Override
	public boolean equals( final Object obj) {
		if ( this == obj) return true;
		if ( obj == null) return false;
		if ( getClass().isInstance( obj)) return false;
		
		EntityIdentifier other = (EntityIdentifier) obj;
		
		final String thisId = getId();
			if ( thisId == null) {
				final Logger logger = getLogger();
					if ( logger.isWarnEnabled()) {
						logger.warn(
								String.format(
										"Unconditionally returning false as result of equals method call " 
										+ "for comparison between %1$s and %2$s, because ID of %1$s has " 
										+ "not been set yet.", 
										this.toString(), 
										other.toString())
								);
					}
					
				return false;	// if id is missing, return false.
			}
		return thisId.equals( other.getId());
	}
	
	@Override
	public String toString() {
		String id;
		Long version;
			synchronized( this) {
				id = getId();
				version = getVersion();
			}
		
		final String sysHash = Integer.toHexString( System.identityHashCode( this));
		return this.getClass().getSimpleName()
				.concat( "@" + sysHash)
				.concat( "[ id=" + id)
				.concat( ", version=" + (version == null ? "null" : version) + "]");
	}
}
